﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using DomainDrivenDesign.Infrastructure.DomainBase;
using DomainDrivenDesign.Infrastructure.RepositoryFramework;
using DomainDrivenDesign.Infrastructure.ChangeTracking;

namespace DomainDrivenDesign.Infrastructure.Test.ChangeTracking
{
    [TestClass]
    public class ChangeContextTest
    {
        #region DummyEntity

        class DummyEntity : KeyAssignableEntityBase, IAggregateRoot
        {
            public DummyEntity() : base() { }
            public DummyEntity(int key) : base(key) { }

            public string Bar { get; set; }            
        }

        class SillyEntity : KeyAssignableEntityBase, IAggregateRoot
        {
            public SillyEntity() : base() { }
            public SillyEntity(int key) : base(key) { }

            public string Moo { get; set; }
        }

        class GoofyEntity : KeyAssignableEntityBase, IAggregateRoot, ICanReconsituteFromSelf
        {
            private string _wup;
            private string _originalWup;

            public GoofyEntity() : base() { }

            public GoofyEntity(int key, string wup)
                : base(key)
            {
                _wup = wup;
                _originalWup = wup;
            }

            public string Wup
            {
                get { return _wup; }
                set { _wup = value; }
            }

            public string OriginalWup
            {
                get { return _originalWup; }
            }

            public IEntity ReconstituteFrom(IEntity mySelf)
            {
                GoofyEntity self = (GoofyEntity)mySelf;

                return new GoofyEntity((int)self.Key, self.Wup);
            }
        }

        #endregion
        
        #region DummyRepo

        class GoofyRepo : RepositoryBase<GoofyEntity>
        {
            public bool PersistUpdatedItemWasCalled { get; set; }

            public GoofyRepo()
                : base()
            {
            }

            public override GoofyEntity FindByKey(object key)
            {
                throw new NotImplementedException();
            }

            public override IEnumerable<GoofyEntity> FindAll()
            {
                throw new NotImplementedException();
            }

            public override void Add(GoofyEntity item)
            {
                throw new NotImplementedException();
            }

            public override void Change(GoofyEntity item)
            {
                PersistUpdatedItemWasCalled = true;
            }

            public override void Remove(GoofyEntity item)
            {
                throw new NotImplementedException();
            }
        }

        #endregion

        #region DummyChangeRule

        class DummyChangeRule : IChangeRule
        {
            public string Foo { get; set; }

            public bool SameRuleAs(IChangeRule another)
            {
                if (!(another is DummyChangeRule))
                    return false;

                if (this.Foo != ((DummyChangeRule)another).Foo)
                    return false;

                return true;
            }

            public override int GetHashCode()
            {
                return this.GetType().GetHashCode() + this.Foo.GetHashCode();
            }
        }

        #endregion

        [TestMethod]
        public void ChangeContext()
        {
            DummyEntity dummy0 = new DummyEntity(20) { Bar = "I'm root" };

            // create with a root entity
            ChangeContext cc = new ChangeContext(dummy0);

            // verify single entity & no rule
            Assert.AreEqual(dummy0, cc.ChangeRoot);
            Assert.IsFalse(cc.HasError);
            Assert.AreEqual(0, cc.GetChangeRules().Count);
            
            DummyEntity[] dummies = new DummyEntity[] { 
                new DummyEntity(19) { Bar = "Ga" },
                new DummyEntity(20) { Bar = "Me" },
                new DummyEntity(21) { Bar = "Ba" }};

            DummyEntity dummy1 = new DummyEntity(21) { Bar = "dummy#1" };
            DummyEntity dummy1a = cc.GetEntity(dummy1);

            // verify GetEntity returns newly added entity
            Assert.AreEqual(dummy1, dummy1a);

            DummyChangeRule rule1 = new DummyChangeRule { Foo = "rule#1" };
            
            cc.AddChange(dummy1, rule1);
            IList<IChangeRule> rules = cc.GetChangeRules();

            // verify AddChange and GetChangeRules with one rule
            Assert.AreEqual(1, rules.Count);
            Assert.AreEqual("rule#1", ((DummyChangeRule)rules[0]).Foo);

            // verify GetEntities 
            Assert.AreEqual(1, cc.GetEntities<DummyEntity>(rules[0]).Count);

            // add another entity with same rule as above but different instance
            DummyEntity dummy2 = new DummyEntity(22) { Bar = "dummy#2" };
            DummyChangeRule rule1a = new DummyChangeRule { Foo = "rule#1" };
            cc.AddChange(dummy2, rule1a);
            rules = cc.GetChangeRules();

            // verify still only one rule, but two entities
            Assert.AreEqual(1, rules.Count);
            Assert.AreEqual("rule#1", ((DummyChangeRule)rules[0]).Foo);
            Assert.AreEqual(2, cc.GetEntities<DummyEntity>(rules[0]).Count);

            SillyEntity silly1 = new SillyEntity(22) { Moo = "silly#1" };
            cc.AddChange(silly1, rule1a);

            // verify still only one rule, but three entities
            Assert.AreEqual(1, rules.Count);
            Assert.AreEqual("rule#1", ((DummyChangeRule)rules[0]).Foo);
            Assert.AreEqual(3, cc.GetEntities<IEntity>(rules[0]).Count);

            DummyChangeRule rule2 = new DummyChangeRule { Foo = "rule#2" };
            cc.AddChange(silly1, rule2);

            // verify there are now 2 rules, one with 3 entries, another one with 1 entry
            Assert.AreEqual(2, cc.GetChangeRules().Count);
            Assert.AreEqual("rule#1", ((DummyChangeRule)rules[0]).Foo);
            Assert.AreEqual(3, cc.GetEntities<IEntity>(rule1).Count);
            Assert.AreEqual(1, cc.GetEntities<IEntity>(rule2).Count);
        }

        [TestMethod]
        public void ChangeContext_ICanReconsitute()
        {
            GoofyEntity goofy0 = new GoofyEntity(20, "i'm root");
            
            // create with a root entity
            ChangeContext cc = new ChangeContext(goofy0);

            // create a repo
            // TODO: How to do this?
            GoofyRepo repo = null; // new GoofyRepo(cc);

            GoofyEntity goofy1 = new GoofyEntity(27, "goofy#1");
            GoofyEntity goofy1a = cc.GetEntity(goofy1);

            goofy1a.Wup = "goofy#1a";

            // verify entity's new values
            Assert.AreEqual("goofy#1", goofy1a.OriginalWup);
            Assert.AreEqual("goofy#1a", goofy1a.Wup);

            repo.Change(goofy1a);
            GoofyEntity goofy1b = cc.GetEntity(goofy1);

            // verify after repo.Change, entity come back in reconsitituted state
            Assert.AreEqual("goofy#1a", goofy1b.OriginalWup);
            Assert.AreEqual("goofy#1a", goofy1b.Wup);

            cc.Commit();

            // verify PersistUpdatedItemWasCalled was called
            Assert.IsTrue(repo.PersistUpdatedItemWasCalled);

            GoofyEntity goofy0a = cc.GetEntity(goofy0);

            goofy0a.Wup = "i got changed";

            repo.Change(goofy0a);

            GoofyEntity goofy0b = cc.GetEntity(goofy0);

            // verify root entity does not get reconstituted
            Assert.AreEqual("i'm root", goofy0b.OriginalWup);

            // TODO: verify root entity do not get commited
            // repo.Chagne will need to skip root entity for now.
        }
    }
}
